package io.github.kimmking.gateway.outbound.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.channel.nio.NioEventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.DefaultEventExecutor;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Promise;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @ClassName NettyHttpUtil
 * @Description
 * @Author Jingxiao.Ye
 * @Date 2021/11/22 上午12:04
 * @Version V1.0
 * @Since JDK 11
 */
public class NettyHttp {

    String url;
    HttpMethod httpMethod;
    Map<String, Iterable<?>> headers;
    byte[] body;

    public static final DefaultEventExecutor eventExecutor = new DefaultEventExecutor(Executors.newCachedThreadPool());

    public NettyHttp(String url, HttpMethod httpMethod, Map<String, Iterable<?>> headers) {
        this.url = url;
        this.httpMethod = httpMethod;
        this.headers = headers;
    }

    public NettyHttp(String url, HttpMethod httpMethod, Map<String, Iterable<?>> headers, byte[] body) {
        this.url = url;
        this.httpMethod = httpMethod;
        this.headers = headers;
        this.body = body;
    }


    //创建一个netty http请求异步请求
    public Future<FullHttpResponse> request() throws MalformedURLException {
        URL nUrl = new URL(url);

        DefaultPromise<FullHttpResponse> promise = new DefaultPromise<>(eventExecutor);
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
        Bootstrap handler = null;
        ChannelFuture cf = null;
        try {
            handler = new Bootstrap().group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.SO_REUSEADDR, true)
                    .option(ChannelOption.SO_RCVBUF, 32 * 1024)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new HttpResponseDecoder())
                                    .addLast(new HttpRequestEncoder())
                                    .addLast(new HttpObjectAggregator(1024 * 1024))
                                    .addLast(new SimpleChannelInboundHandler<FullHttpResponse>() {
                                        @Override
                                        protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception {
                                            promise.setSuccess(msg);
                                            ctx.close();
                                        }

                                        @Override
                                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                            DefaultFullHttpRequest request = null;
                                            if (body == null) {
                                                request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, httpMethod,nUrl.getPath());
                                            } else {
                                                request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, httpMethod, nUrl.getPath()
                                                        , ctx.alloc().buffer().writeBytes(body));
                                            }

                                            HttpHeaders headers = request.headers();
                                            if (NettyHttp.this.headers != null) {
                                                for (Map.Entry<String, Iterable<?>> entry : NettyHttp.this.headers.entrySet()) {
                                                    headers.add(entry.getKey(), entry.getValue());
                                                }
                                            }

                                            ctx.writeAndFlush(request);
                                        }
                                    });
                        }

                    });
            int port = nUrl.getPort();
            String protocol = nUrl.getProtocol();
            if(port == -1){
                port = "https".equals(protocol)?443:80;
            }
            cf = handler.connect(nUrl.getHost(),port).sync();
            cf.channel().closeFuture();
        } catch (Exception e) {
            eventLoopGroup.shutdownGracefully();
            promise.setFailure(e);
        }
        return promise;
    }
}
